/*
 * This file or a portion of this file is licensed under the terms of
 * the Globus Toolkit Public License, found in file GTPL, or at
 * http://www.globus.org/toolkit/download/license.html. This notice must
 * appear in redistributions of this file, with or without modification.
 *
 * Redistributions of this Software, with or without modification, must
 * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
 * some other similar material which is provided with the Software (if
 * any).
 *
 * Copyright 1999-2004 University of Chicago and The University of
 * Southern California. All rights reserved.
 */
package org.griphyn.vdl.toolkit;

import java.io.*;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.Iterator;
import edu.isi.pegasus.common.util.Version;
import edu.isi.pegasus.common.util.Separator;
import org.griphyn.vdl.parser.VDLxParser;
import org.griphyn.vdl.classes.*;
import org.griphyn.vdl.router.*;
import org.griphyn.vdl.dbschema.*;
import org.griphyn.vdl.util.Logging;
import org.griphyn.vdl.util.ChimeraProperties;
import org.griphyn.vdl.directive.*;
import gnu.getopt.*;

/**
 * This class generates the DAX per the request for an lfn or a derivation.
 *
 * @author Jens-S. Vöckler
 * @author Yong Zhao
 * @version $Revision: 2079 $
 *
 * @see org.griphyn.vdl.router.Route
 * @see org.griphyn.vdl.router.BookKeeper
 */
public class GetDAX extends Toolkit 
{
  /**
   * ctor: Constructs a new instance object with the given application name.
   */
  public GetDAX( String appName )
  {
    super(appName);
  }

  /**
   * Implements printing the usage string onto stdout.
   */
  public void showUsage()
  {
    String linefeed = System.getProperty( "line.separator", "\r\n" );

    System.out.println(
"$Id: GetDAX.java 2079 2010-04-19 23:31:11Z vahi $" + linefeed +
"VDS version " + Version.instance().toString() + linefeed );

    System.out.println(
"Usage: " + this.m_application + ' ' + "[-d dbx] [-l label] [-m max] [-o DAX] -n ns -i id -v ver" + 
linefeed +
"   or: " + this.m_application + ' ' + "[-d dbx] [-l label] [-m max] [-o DAX] -f f1[,..] -F lof" + 
linefeed + 
"       -D d1[,..] -L lod" ); 

    System.out.println( linefeed + 
"Generic options: " + linefeed +
" -V|--version      print version information and exit." + linefeed +
" -d|--dbase dbx    associates the dbname with the database, unused." + linefeed +
"    --verbose      increases the verbosity level." + linefeed +
" -l|--label label  uses the specified label for the output DAX, default \"test\"." + linefeed +
" -o|--output DAX   writes the generated results into outfn, default is stdout." + linefeed +
" -m|--maxdepth max only search up to the specified depth, default is " + Route.MAXIMUM_DEPTH + "." + linefeed +
"                   For complex or large graphs, you may want to increase this." + linefeed +
" -X|--xmlns prefix uses an XML namespace prefix for the generated DAX document." + linefeed +
linefeed +
"Group 1: At least one of these must be used, but none of group 2 below:" + linefeed +
" -n|--namespace ns uses namespace ns to search for matching DVs, default null." + linefeed +
" -i|--name name    uses the specified DV name to search for it, required." + linefeed +
" -v|--ver vs       uses the specified version to narrow DVs, default null." + linefeed +
linefeed +
"Group 2: All of these may be mixed, but not used with group 1 above:" + linefeed +
" -f|--file fn[,..] requests LFN or a list of LFNs to be materialized." + linefeed +
" -f|--lfn          is a synonym for the --file option." + linefeed +
" -F|--filelist lof read the LFNs from file lolfn, one per line." + linefeed +
" -D|--dv dv[,..]   requests the DV or list of DVs to be produced. Each argument" + linefeed +
"                   is a fully-qualified derivation name namespace::name:version" + linefeed +
"                   with the usual omission rules applying." + linefeed +
" -D|--derivation   is a synonym for the --dv option." + linefeed +
" -L|--dvlist lodvs read the DVs from file lodvs, one per line." + linefeed );
    
    System.out.println( "The following exit codes are produced:" + linefeed +
" 0  :-)  Success" + linefeed +
" 1  :-|  Empty result while " + m_application + " still ran successfully." + linefeed +
" 2  :-(  Runtime error detected by " + m_application + ", please read the message." + linefeed +
" 3  8-O  Fatal error merits a program abortion. Please carefully check your" + linefeed +
"         configuration files and setup before filing a bug report." + linefeed );
  }

  /**
   * Creates a set of options.
   */
  protected LongOpt[] generateValidOptions()
  {
    LongOpt[] lo = new LongOpt[20];

     lo[0] = new LongOpt( "dbase", LongOpt.REQUIRED_ARGUMENT, null, 'd' );
     lo[1] = new LongOpt( "version", LongOpt.NO_ARGUMENT, null, 'V' );
     lo[2] = new LongOpt( "help", LongOpt.NO_ARGUMENT, null, 'h' );
     lo[3] = new LongOpt( "verbose", LongOpt.NO_ARGUMENT, null, 1 );

     lo[4] = new LongOpt( "label", LongOpt.REQUIRED_ARGUMENT, null, 'l' );
     lo[5] = new LongOpt( "output", LongOpt.REQUIRED_ARGUMENT, null, 'o' );
     lo[6] = new LongOpt( "maxdepth", LongOpt.REQUIRED_ARGUMENT, null, 'm' );
     lo[7] = new LongOpt( "xmlns", LongOpt.REQUIRED_ARGUMENT, null, 'X' );

     lo[8] = new LongOpt( "namespace", LongOpt.REQUIRED_ARGUMENT, null, 'n' );
     lo[9] = new LongOpt( "ns", LongOpt.REQUIRED_ARGUMENT, null, 'n' );
    lo[10] = new LongOpt( "name", LongOpt.REQUIRED_ARGUMENT, null, 'i' );
    lo[11] = new LongOpt( "id", LongOpt.REQUIRED_ARGUMENT, null, 'i' );
    lo[12] = new LongOpt( "ver", LongOpt.REQUIRED_ARGUMENT, null, 'v' );
    lo[13] = new LongOpt( "vs", LongOpt.REQUIRED_ARGUMENT, null, 'v' );
    
    lo[14] = new LongOpt( "file", LongOpt.REQUIRED_ARGUMENT, null, 'f' );
    lo[15] = new LongOpt( "lfn", LongOpt.REQUIRED_ARGUMENT, null, 'f' );
    lo[16] = new LongOpt( "filelist", LongOpt.REQUIRED_ARGUMENT, null, 'F' );
    lo[17] = new LongOpt( "derivation", LongOpt.REQUIRED_ARGUMENT, null, 'D' );
    lo[18] = new LongOpt( "dv", LongOpt.REQUIRED_ARGUMENT, null, 'D' );
    lo[19] = new LongOpt( "dvlist", LongOpt.REQUIRED_ARGUMENT, null, 'L' );

    return lo;
  }

  /**
   * Get the request for a dv or an lfn and generate the corresponding
   * DAX.
   */
  public static void main(String[] args) 
  {
    int result = 0;
    int verbose = 0;
    Writer bw = null;
    DatabaseSchema dbschema = null;
    GetDAX me = null;

    try {
      me = new GetDAX("gendax");
      if ( args.length == 0 ) {
	me.showUsage();
	return ;
      }

      // get the commandline options
      Getopt opts = new Getopt( me.m_application, args, 
				"hd:m:n:i:v:o:f:l:D:F:L:VX:", 
				me.generateValidOptions() );
      opts.setOpterr(false);

      boolean flag = false;
      String xmlns = null;
      String arg = null; 
      String label = null;
      String dbase = null;
      String ns = null;
      String id = null;
      String vs = null;
      String fn = null;
      int maxdepth = -1;
      ArrayList derivations = new ArrayList();
      ArrayList filenames = new ArrayList();

      int option = 0;
      while ( (option = opts.getopt()) != -1 ) {
	switch ( option ) {
	case 1:
	  verbose++;
	  me.increaseVerbosity();
	  break;

	case 'V':
	  System.out.println( "$Id: GetDAX.java 2079 2010-04-19 23:31:11Z vahi $" );
	  System.out.println( "VDS version " + Version.instance().toString() );
	  return;
	
	case 'X':
	  xmlns = opts.getOptarg();
	  break;

	case 'D':
	  arg = opts.getOptarg();
	  if ( arg != null ) {
	    StringTokenizer st = new StringTokenizer( arg, ",", false );
	    while ( st.hasMoreTokens() ) derivations.add( st.nextToken() );
	  }
	  break;

	case 'd':
	  dbase = opts.getOptarg();
	  break;

	case 'F':
	  arg = opts.getOptarg();
	  if ( arg != null ) {
	    int nr = me.readFile( arg, filenames );
	    me.m_logger.log( "app", 1, "read " + nr + " LFNs from " + arg );
	  }
	  break;

	case 'f':
	  arg = opts.getOptarg();
	  if ( arg != null ) {
	    StringTokenizer st = new StringTokenizer( arg, ",", false ); 
	    while ( st.hasMoreTokens() ) filenames.add( st.nextToken() );
	  }
	  break;

	case 'L':
	  arg = opts.getOptarg();
	  if ( arg != null ) {
	    int nr = me.readFile( arg, derivations );
	    me.m_logger.log( "app", 1, "read " + nr + " DV names from " + arg );
	  }
	  break;

	case 'l':
	  arg = opts.getOptarg();
	  if ( arg != null ) label = arg;
	  break;

	case 'm':
	  arg = opts.getOptarg();
	  if ( arg != null ) maxdepth = Integer.valueOf(arg).intValue();
	  break;

	case 'n':
	  arg = opts.getOptarg();
	  if ( arg != null ) {
	    ns = arg;
	    flag = true;
	  }
	  break;
	  
	case 'i':
	  arg = opts.getOptarg();
	  if ( arg != null ) {
	    id = arg;
	    flag = true;
	  }
	  break;
	  
	case 'v':
	  arg = opts.getOptarg();
	  if ( arg != null ) {
	    vs = arg;
	    flag = true;
	  }
	  break;

	case 'o':
	  arg = opts.getOptarg();
	  if ( arg != null ) fn = arg;
	  break;

	case 'h':
	  me.showUsage();
	  return;

	default:
	  me.showUsage();
	  throw new RuntimeException( "Incorrect option or option usage" );
	}
      }

      // you can use either the pattern match, or -D and -f
      int group2 = derivations.size() + filenames.size();
      if ( ! flag && group2 == 0 ) {
	me.showUsage();
	throw new RuntimeException( "You must specify either group 1 (-n -i -v) or group 2 (-f -F -L -D) options!");
      }

      // bug#90/91: warn about mixing options groups
      if ( flag && group2 > 0 ) {
	me.m_logger.log( "default", 0, "Warning: You are mixing group 1 and group 2 options!" );
      }

      // bug#90/91: require name in group1 options.
      if ( flag && ( id == null || id.length() == 0 ) ) {
	throw new RuntimeException( "Group 1 options require -i|--name" );
      }

      // Connect the database.
      String schemaName = ChimeraProperties.instance().getVDCSchemaName();

      Connect connect = new Connect();
      dbschema = connect.connectDatabase(schemaName);
      Explain explain = new Explain(dbschema);

      // constraint on search
      if ( maxdepth >= 0 ) explain.setMaximumDepth(maxdepth);

      // Make the request.
      if ( flag ) 
	explain.requestDerivation( ns, id, vs );
      if ( filenames.size() > 0 ) 
	explain.requestLFN( filenames );
      if ( derivations.size() > 0 ) {
	int size = derivations.size();
	int count=1;
	for ( Iterator i=derivations.iterator(); i.hasNext(); count++ ) {
	  String what = (String) i.next(); 
	  me.m_logger.log( "app", 2, "requesting " + what + " (" + 
			   count + "/" + size + ")" );
	  explain.requestDerivation(what);
	}
      }
      
      // show the DAX
      if ( fn != null ) {
	// save to file
	me.m_logger.log( "app", 1, "saving output to " + fn );
	bw = new BufferedWriter(new FileWriter(fn));
      } else {
	// somebody may have redirected stdout, thus buffer!
	bw = new BufferedWriter(new PrintWriter(System.out));
      }

      // write resulting DAX
      explain.writeDAX( bw, label, xmlns );
      bw.flush();
      bw.close();
      
      // fail on empty requested?
      if ( explain.isEmpty() ) {
	System.err.println( "ERROR: The resulting DAX is empty" );
	result = 1;
      }

      dbschema.close();
    } catch ( IOException ioe ) {
      System.err.println( "IO ERROR: " + ioe.getMessage() );
      if ( verbose > 0 ) ioe.printStackTrace( System.err );
      result = 2;
    } catch ( RuntimeException rte ) {
      System.err.println( "RT ERROR: " + rte.getMessage() );
      if ( verbose > 0 ) rte.printStackTrace( System.err );
      result = 2;
    } catch( Exception e ) {
      e.printStackTrace( System.err );
      System.err.println( "FATAL: " + e.getMessage() );
      result = 3; 
    } 
    if ( result != 0 ) System.exit(result);
  }
}


